<?php
declare(strict_types=1);

namespace WebApp\lib\co\core;

use ErrorException;
use Generator;
use SplQueue;

/**
 * Class Scheduler
 * @package WebApp\lib\co\core
 */
class Scheduler
{
    protected $max_task_id = 0;
    public $task_map = []; // taskId => task
    protected $task_queue;
    public $name;
    private $max = 100;

    /**
     * Scheduler constructor.
     * @throws ErrorException
     */
    public function __construct()
    {
        $this->task_queue = new SplQueue();
        try {
            $this->name = microtime(true) . random_int(0, 1000000);
        } catch (\Exception $e) {
            throw new ErrorException();
        }
//        var_dump($this->name);

        if (!isset($GLOBALS['SCHEDULER'])) {
            $GLOBALS['SCHEDULER'] = [];
            $GLOBALS['SCHEDULER_COUNT'] = 0;
        }

        if (isset($GLOBALS['SCHEDULER'][$this->name])) {
            throw new ErrorException();
        }

        $GLOBALS['SCHEDULER'][$this->name] = $this;
    }

    /**
     * @param Generator $coroutine
     * @param int $pid
     * @return int
     */
    public function newTask(Generator $coroutine, $pid = 0)
    {
        $tid = ++$this->max_task_id;
        $this->task_map[$tid] = new Task($tid, $coroutine, $pid);
        $this->schedule($this->task_map[$tid]);
        if ($this->start === false) {
            $this->start = true;
            swoole_timer_after(1, [$this, 'timer']);
        }
        return $tid;
    }

    /**
     * @param Task $task
     */
    public function schedule(Task $task)
    {
        //var_dump('add:' . $task->task_id);
        $this->task_queue->enqueue($task);
        if ($this->start === false) {
            $this->start = true;
            swoole_timer_after(1, [$this, 'timer']);
        }
    }

    public function timer()
    {
        try {
            if (!$this->task_queue->isEmpty()) {
                $task = $this->task_queue->dequeue();
                //var_dump('run:' . $task->task_id);
                /** @var Task $task */
                $retval = $task->run();

                if ($retval instanceof SystemCall) {
                    //var_dump($retval->name);
                    $retval($task, $this);
                    throw new ErrorException();
                }

                if ($task->isFinished()) {
//                    unset($this->task_map[$task->task_id]);
                    throw new ErrorException();
                }

                $this->schedule($task);
                throw new ErrorException();
            } else {
                $this->start = false;
                if ($this->count <= 0) {
                    if ($this->wait_all !== false) {
                        /** @var Scheduler $s */
                        $s = $GLOBALS['SCHEDULER'][$this->wait_all[0]];
                        $s->schedule($s->task_map[$this->wait_all[1]]);
                        $this->wait_all = false;
                    }
                }
            }
        } catch (ErrorException $e) {
            if ($GLOBALS['SCHEDULER_COUNT'] > $this->max) {
                swoole_timer_after((int)($GLOBALS['SCHEDULER_COUNT']/$this->max) * 1000, [$this, 'timer']);
            } else {
                swoole_timer_after(1, [$this, 'timer']);
            }
        }
    }

    private $start = false;
    public $count = 0;
    /**
     * @param $tid
     * @return bool
     */
    public function killTask($tid)
    {
        if (!isset($this->task_map[$tid])) {
            return false;
        }

        unset($this->task_map[$tid]);

        // This is a bit ugly and could be optimized so it does not have to walk the queue,
        // but assuming that killing tasks is rather rare I won't bother with it now
        foreach ($this->task_queue as $i => $task) {
            if ($task->get_task_id() === $tid) {
                unset($this->task_queue[$i]);
                break;
            }
        }

        return true;
    }

    private $wait_all = false;

    /**
     * @param string $schedu_name
     * @param int $id
     */
    public function addEvent(string $schedu_name, int $id)
    {
        $this->wait_all = [
            $schedu_name,
            $id,
        ];
    }
}
